package com.components.cachewebview

import android.content.Context
import android.text.TextUtils
import com.blankj.utilcode.util.FileUtils
import com.blankj.utilcode.util.NetworkUtils
import com.components.cachewebview.config.CacheExtensionConfig
import com.components.cachewebview.utils.MimeTypeMapUtils
import com.components.cachewebview.utils.NetUtils
import com.components.cachewebview.utils.OKHttpFileUtils
import com.tencent.smtt.export.external.interfaces.WebResourceRequest
import com.tencent.smtt.export.external.interfaces.WebResourceResponse
import com.tencent.smtt.sdk.URLUtil
import com.tencent.smtt.sdk.WebView
import okhttp3.*
import java.io.*
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager

class WebViewCacheInterceptor(builder: Builder) : WebViewRequestInterceptor {

    private var mCacheFile: File? = null
    private var mDynamicCacheFile: File? = null
    private var mCacheSize: Long = 0
    private var mConnectTimeout: Long = 0
    private var mReadTimeout: Long = 0
    private var mCacheExtensionConfig: CacheExtensionConfig? = null
    private var mContext: Context? = null
    private var mDebug = false
    private var mCacheType: CacheType? = null
    private var mAssetsDir: String? = null
    private var mTrustAllHostname = false
    private var mSSLSocketFactory: SSLSocketFactory? = null
    private var mX509TrustManager: X509TrustManager? = null
    private var mDns: Dns? = null
    private var mResourceInterceptor: ResourceInterceptor? = null
    private var mIsSuffixMod = false

    private var mHttpClient: OkHttpClient? = null
    private var mOrigin = ""
    private var mReferer = ""
    private var mUserAgent = ""

    companion object {
        const val KEY_CACHE = "WebResourceInterceptor-Key-Cache"

        class Builder(context: Context) {
            var mCacheFile: File? = null
            var mDynamicCacheFile: File? = null
            var mCacheSize = (100 * 1024 * 1024).toLong()
            var mConnectTimeout: Long = 20
            var mReadTimeout: Long = 20
            var mCacheExtensionConfig: CacheExtensionConfig? = null
            var mContext: Context = context
            var mDebug = true
            val mCacheType = CacheType.FORCE

            var mTrustAllHostname = false
            var mSSLSocketFactory: SSLSocketFactory? = null
            var mX509TrustManager: X509TrustManager? = null
            var mResourceInterceptor: ResourceInterceptor? = null

            var mAssetsDir: String? = null
            var mIsSuffixMod = false
            var mDns: Dns? = null

            init {
                mCacheFile = File(context.cacheDir.toString(), "CacheWebViewCache")
                mCacheExtensionConfig = CacheExtensionConfig
            }

            fun setResourceInterceptor(resourceInterceptor: ResourceInterceptor) {
                mResourceInterceptor = resourceInterceptor
            }

            fun setTrustAllHostname(): Builder {
                mTrustAllHostname = true
                return this
            }

            fun setSSLSocketFactory(
                sslSocketFactory: SSLSocketFactory?,
                x509TrustManager: X509TrustManager?
            ): Builder {
                if (sslSocketFactory != null && x509TrustManager != null) {
                    mSSLSocketFactory = sslSocketFactory
                    mX509TrustManager = x509TrustManager
                }
                return this
            }

            fun setCachePath(file: File?): Builder {
                if (file != null) {
                    mCacheFile = file
                }
                return this
            }

            fun setDynamicCachePath(file: File?): Builder {
                if (file != null) {
                    mDynamicCacheFile = file
                }
                return this
            }

            fun setCacheSize(cacheSize: Long): Builder {
                if (cacheSize > 1024) {
                    mCacheSize = cacheSize
                }
                return this
            }

            fun setReadTimeoutSecond(time: Long): Builder {
                if (time > 0) {
                    mReadTimeout = time
                }
                return this
            }

            fun setConnectTimeoutSecond(time: Long): Builder {
                if (time > 0) {
                    mConnectTimeout = time
                }
                return this
            }

            fun setCacheExtensionConfig(config: CacheExtensionConfig?): Builder {
                if (config != null) {
                    mCacheExtensionConfig = config
                }
                return this
            }

            fun setDebug(debug: Boolean): Builder {
                mDebug = debug
                return this
            }

            fun setCacheType(suffixMod: Boolean): Builder {
                mIsSuffixMod = suffixMod
                return this
            }

            fun setAssetsDir(dir: String?): Builder {
                if (dir != null) {
                    mAssetsDir = dir
                }
                return this
            }

            fun setDns(dns: Dns): Builder {
                mDns = dns
                return this
            }

            fun build(): WebViewRequestInterceptor {
                return WebViewCacheInterceptor(this)
            }

        }

    }

    init {
        this.mCacheExtensionConfig = builder.mCacheExtensionConfig
        this.mCacheFile = builder.mCacheFile
        this.mDynamicCacheFile = builder.mDynamicCacheFile
        this.mCacheSize = builder.mCacheSize
        this.mCacheType = builder.mCacheType
        this.mConnectTimeout = builder.mConnectTimeout
        this.mReadTimeout = builder.mReadTimeout
        this.mContext = builder.mContext
        this.mDebug = builder.mDebug
        this.mAssetsDir = builder.mAssetsDir
        this.mX509TrustManager = builder.mX509TrustManager
        this.mSSLSocketFactory = builder.mSSLSocketFactory
        this.mTrustAllHostname = builder.mTrustAllHostname
        this.mResourceInterceptor = builder.mResourceInterceptor
        this.mIsSuffixMod = builder.mIsSuffixMod
        this.mDns = builder.mDns
        initHttpClient()
        if (isEnableAssets()) {
            initAssetsLoader()
        }
    }

    private fun isEnableDynamicCache(): Boolean {
        return mDynamicCacheFile != null
    }

    private fun isEnableAssets(): Boolean {
        return mAssetsDir != null
    }

    private fun initAssetsLoader() {
        mAssetsDir?.let {
            AssetsLoader.getInstance()?.init(mContext)?.setDir(it)?.isAssetsSuffixMod(mIsSuffixMod)
        }
    }

    private fun initHttpClient() {
        val cache = mCacheFile?.let { Cache(it, mCacheSize) }
        val builder = OkHttpClient.Builder()
            .cache(cache)
            .connectTimeout(mConnectTimeout, TimeUnit.SECONDS)
            .readTimeout(mReadTimeout, TimeUnit.SECONDS)
            .addNetworkInterceptor(HttpCacheInterceptor())
        if (mTrustAllHostname) {
            builder.hostnameVerifier { _, _ -> true }
        }
        if (mSSLSocketFactory != null && mX509TrustManager != null) {
            builder.sslSocketFactory(mSSLSocketFactory!!, mX509TrustManager!!)
        }
        if (mDns != null) {
            builder.dns(mDns!!)
        }
        mHttpClient = builder.build()
    }

    override fun interceptRequest(request: WebResourceRequest?): WebResourceResponse? {
        return interceptRequest(request?.url.toString(), request?.requestHeaders)
    }

    override fun interceptRequest(url: String): WebResourceResponse? {
        return interceptRequest(url, buildHeaders())
    }

    private fun buildHeaders(): MutableMap<String, String> {
        val headers: MutableMap<String, String> = mutableMapOf()
        if (!TextUtils.isEmpty(mOrigin) && "null" != mOrigin) {
            headers["mOrigin"] = mOrigin
        }
        if (!TextUtils.isEmpty(mReferer) && "null" != mReferer) {
            headers["Referer"] = mReferer
        }
        if (!TextUtils.isEmpty(mUserAgent) && "null" != mUserAgent) {
            headers["User-Agent"] = mUserAgent
        }
        return headers
    }

    override fun getCachePath(): File? {
        return mCacheFile
    }

    override fun clearCache() {
        FileUtils.delete(mCacheFile?.absolutePath)
        AssetsLoader.getInstance()?.clear()
    }

    override fun enableForce(force: Boolean) {
        mCacheType = if (force) {
            CacheType.FORCE
        } else {
            CacheType.NORMAL
        }
    }

    override fun getCacheFile(url: String): InputStream? {
        return OKHttpFileUtils.getCacheFile(mCacheFile, url)
    }

    override fun initAssetsData() {
        AssetsLoader.getInstance()?.initData()
    }

    override fun loadUrl(webView: WebView?, url: String) {
        if (!isValidUrl(url)) {
            return
        }
        webView?.loadUrl(url)
        mReferer = webView?.url.toString()
        mOrigin = NetUtils.getOriginUrl(mReferer)
        mUserAgent = webView?.settings?.userAgentString.toString()
    }

    override fun loadUrl(url: String, userAgent: String) {
        if (!isValidUrl(url)) {
            return
        }
        mReferer = url
        mOrigin = NetUtils.getOriginUrl(mReferer)
        mUserAgent = userAgent
    }

    override fun loadUrl(
        url: String,
        additionalHttpHeaders: Map<String?, String?>?,
        userAgent: String
    ) {
        if (!isValidUrl(url)) {
            return
        }
        mReferer = url
        mOrigin = NetUtils.getOriginUrl(mReferer)
        mUserAgent = userAgent
    }

    override fun loadUrl(
        webView: WebView?,
        url: String,
        additionalHttpHeaders: Map<String, String>
    ) {
        if (!isValidUrl(url)) {
            return
        }
        webView?.loadUrl(url, additionalHttpHeaders)
        mReferer = webView?.url.toString()
        mOrigin = NetUtils.getOriginUrl(mReferer)
        mUserAgent = webView?.settings?.userAgentString.toString()
    }

    private fun checkUrl(url: String): Boolean {
        if (TextUtils.isEmpty(url)) {
            return false
        }
        if (!url.startsWith("http")) {
            return false
        }
        if (mResourceInterceptor != null && !mResourceInterceptor!!.interceptor(url)) {
            return false
        }
        val extension = MimeTypeMapUtils.getFileExtensionFromUrl(url)
        if (TextUtils.isEmpty(extension)) {
            return false
        }
        if (mCacheExtensionConfig?.isMedia(extension) == true) {
            return false
        }
        if (mCacheExtensionConfig?.canCache(extension) == true) {
            return false
        }
        return true
    }

    private fun addHeader(
        reqBuilder: okhttp3.Request.Builder,
        headers: MutableMap<String, String>?
    ) {
        if (headers == null) {
            return
        }
        headers.entries.forEach {
            reqBuilder.addHeader(it.key, it.value)
        }
    }

    private fun interceptRequest(
        url: String,
        headers: MutableMap<String, String>?
    ): WebResourceResponse? {
        if (mCacheType == CacheType.NORMAL) {
            return null
        }
        if (!checkUrl(url)) {
            return null
        }

        if (mDynamicCacheFile != null) {
            val file: File? = DynamicCacheLoader.getInstance()?.getResByUrl(mDynamicCacheFile!!, url)
            if (file != null) {
                val mimeTypeFromUrl = MimeTypeMapUtils.getMimeTypeFromUrl(url)
                var inputStream: InputStream? = null
                try {
                    inputStream = FileInputStream(file)
                } catch (e: FileNotFoundException) {
                    e.stackTrace
                }
                return WebResourceResponse(mimeTypeFromUrl, "", inputStream)
            }
        }

        if (mAssetsDir != null) {
            val inputStream: InputStream? = AssetsLoader.getInstance()?.getResByUrl(url)
            if (inputStream != null) {
                val mimeType = MimeTypeMapUtils.getMimeTypeFromUrl(url)
                return WebResourceResponse(mimeType, "", inputStream)
            }
        }

        try {
            val reqBuilder = Request.Builder().url(url)
            val extension = MimeTypeMapUtils.getFileExtensionFromUrl(url)
            if (mCacheExtensionConfig?.isHtml(extension) == true) {
                headers?.set(KEY_CACHE, mCacheType?.ordinal.toString())
            }
            addHeader(reqBuilder, headers)

            if (!NetworkUtils.isConnected()) {
                reqBuilder.cacheControl(CacheControl.FORCE_CACHE)
            }
            val request = reqBuilder.build()
            val response = mHttpClient?.newCall(request)?.execute()
            val mimeType = MimeTypeMapUtils.getMimeTypeFromUrl(url)
            val webResourceResponse =
                WebResourceResponse(mimeType, "", response?.body?.byteStream())
            if (response?.code == 504 && !NetworkUtils.isConnected()) {
                return null
            }
            var message = response?.message
            if (TextUtils.isEmpty(message)) {
                message = "OK"
            }
            try {
                if (response != null && message != null) {
                    webResourceResponse.setStatusCodeAndReasonPhrase(response.code, message)
                }
            } catch (e: Exception) {
                return null
            }
            webResourceResponse.responseHeaders =
                NetUtils.multimapToSingle(response?.headers?.toMultimap())
            return webResourceResponse
        } catch (e: IOException) {
            e.stackTrace
        }
        return null
    }

    private fun isValidUrl(url: String?): Boolean {
        return URLUtil.isValidUrl(url)
    }

}